#ifndef __GLF_IO_FS_INCLUDED__
#define __GLF_IO_FS_INCLUDED__

#include <glf/config.h>

#include <glf/io/stream.h>

namespace glf {

namespace fs {


enum perm {
	read		= 0x01,
	write		= 0x02,
	exec		= 0x04,

	owner		= 0x10,
	group		= 0x20,
	other		= 0x40,

	rw			= read|write,
	rwx			= read|write|exec,
	
	owner_read	= owner|read,
	owner_write	= owner|write,
	owner_exec	= owner|exec,

	group_read	= group|read,
	group_write	= group|write,
	group_exec	= group|exec,

	other_read	= other|read,
	other_write	= other|write,
	other_exec	= other|exec
};

struct FileInfo {
	stringc filename;
	stringc fullpath;
	const char* path; //without filename
	int64 timeCreated;
	int64 timeLastModified;
	int64 timeLastAccessed;
	perm permissions;
	size_t size;
	bool isDir;
	bool isHidden;

	FileInfo() {
		isDir = false;
		isHidden = false;
	}
};

//bool CheckRequiredSize(size_t& size);
//bool Init(const char* dataRoot);
//bool Deinit();

struct DirHandle : public ios {
	

	DirHandle();
	DirHandle(const char* path, openflag flag=loc_data|list_all);
	DirHandle(const DirHandle& handle, const char* path, openflag flag=loc_data|list_all);
	
	~DirHandle();
	
	bool FindFirst(const char* path, openflag flag=loc_data|list_all);
	bool FindFirst(const DirHandle& handle, const char* path, openflag flag=loc_data|list_all);
	bool FindNext();
	void Close();

	bool _Filter();

	Error GetLastError() const;
	openflag GetFlag() const;

	bool MkDir(const char* dir);

	FileInfo fileInfo;
	Error mLastError;
	stringc mPath;
	openflag mFlag;

	GLF_DECLARE_IMPL(2100)
};

bool MoveFile(const char* src, const char* dst);
bool RemoveFile(const char* file);
bool CopyFile(const char* src, const char* dst);

bool DoesFileExist(const char* path);
bool DoesFileExist(const char* path, ios::openflag flag);

bool MkDir(const char* dir, ios::openflag flag = ios::loc_home);
bool RmDir(const char* dir);
bool MoveDir(const char* dir);

bool GetInfo(const char* path, FileInfo& info, ios::openflag flag = ios::loc_data);

bool GetLastError();

bool MountDrive(const char* drive, const char* dst, ios::openflag flag);
ios::openflag ResolvePath(const char* path, ios::openflag flag, char* newPath, size_t newPathLength);
bool UnmountDrive(const char* drive);

const char * GetMountedDrive(const char* name);

const char* GetHomeDir();
const char* GetTmpDir();
const char* GetCurDir();

void SetAlternateDataDir(size_t idx, const char* dir);
const char* GetAlternateDataDir(size_t idx);
size_t GetAlternateDataDirCount();

bool IsUsingAlternateDataDirs();
void SetIsUsingAlternateDataDirs( bool isEnabled );

void SetCurDir(const char* path);

} //namespace fs

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

/*
 Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
 It should be understood that this may change the meaning of the path
 if it contains symbolic links!
 return number of character written
*/
size_t NormalizePath(char* dst, size_t dstLen, const char* src);

void VJoinPath(char* dst, size_t dstLen, size_t numToJoin, va_list vlist);
void VJoinPath(char* dst, size_t dstLen, size_t numToJoin, ...);

template<size_t N>
inline void VJoinPath(char dst[N], size_t numToJoin, ...) {
	va_list vlist;
	
	va_start(vlist, numToJoin);
	
	VJoinPath(dst, N, numToJoin, vlist);

	va_end(vlist);
}

template<size_t N>
inline void JoinPath(char (&dst)[N], const char* path1, const char* path2) {
	VJoinPath(dst, N, 2, path1, path2);
}

template<size_t N>
inline void JoinPath(char (&dst)[N], const char* path1, const char* path2, const char* path3) {
	VJoinPath(dst, N, 3, path1, path2, path3);
}

template<size_t N>
inline void JoinPath(char (&dst)[N], const char* path1, const char* path2, const char* path3, const char* path4) {
	VJoinPath(dst, N, 4, path1, path2, path3, path4);
}

void AppendPath(stringc& src, const stringc& path0);
void AppendPath(stringc& src, const stringc& path0, const stringc& path1);
void AppendPath(stringc& src, const stringc& path0, const stringc& path1, const stringc& path2);

stringc JoinPath(const stringc& path0);
stringc JoinPath(const stringc& path0, const stringc& path1);
stringc JoinPath(const stringc& path0, const stringc& path1, const stringc& path2);

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

enum FsError {
	FSERROR_NONE,
	//
	FSERROR_COUNT
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

using fs::FileInfo;
class App;

#undef CopyFile

class Fs : private NonCopyable, public ios {
	static Fs* sFs;
public:
	static Fs& Get() {
		return *sFs;
	}
	static Fs* Inst() {
		return sFs;
	}

	void Win32OverrideDataPath(const char* newPath);
	void IphoneOverrideDataPath(const char* newPath);
	void MacOSXOverrideDataPath(const char* newPath);
	
	void ClearHomeDataDir();
	void UpdateHomeDataDir();
	void _CopyDataToHomeData(fs::DirHandle& dh);

	bool MoveFile(const char* src, const char* dst);
	bool RemoveFile(const char* file);
	bool CopyFile(const char* src, openflag srcLoc, const char* dst, openflag dstLoc);

	bool DoesFileExist(const char* path);
	bool DoesFileExist(const char* path, openflag flag);

	bool MkDir(const char* dir, openflag flag = ios::loc_home);
	bool RmDir(const char* dir, openflag flag = ios::loc_home); //add recursive param?
	bool EmptyDir(const char* dir, openflag flag = ios::loc_home);

	bool GetInfo(const char* path, FileInfo& info, openflag flag=loc_data);

	FsError GetLastError();

	openflag ResolvePath(const char* path, openflag flag, char* newPath, size_t newPathLength);
	bool MountDrive(const char* drive, const char* dst, openflag flag);
	bool UnmountDrive(const char* drive);
	const char * GetMountedDrive(const char* name);

	const char* GetDir(openflag loc) const;
	void SetDir(openflag loc, const char* newPath);

	const char* GetDataDir();
	const char* GetHomeDir();
	const char* GetTmpDir();
	
	void SetDataDir(const char* newPath);
	void SetHomeDir(const char* newPath);
	void SetTempDir(const char* newPath);
	
    bool LoadObfuscationMap(const char* filename, unsigned char key);

	bool IsForcingLowercase() const { return mIsForcingLowercase; }
	void SetIsForcingLowercase(bool isForcingLowercase) {mIsForcingLowercase = isForcingLowercase;}

private:
	friend struct GlobalsBase;
	Fs();
	~Fs();

private:
	bool mIsForcingLowercase;

	stringc mDataDir;
	stringc mHomeDir;
	stringc mTmpDir;
	stringc mBakDir;
	stringc mRawDir;
	stringc mNocopyDir;
	mutable stringc mCwdDir;

	stringc mCwdStack[4];
	uint mCwdStackIdx;

	struct Link {
		stringc name;
		stringc destination;
		openflag flag;
	};
	
	Link  mLinks[16];

	uint mNumLinked;
	Link* mLinked[16];

	GLF_DECLARE_IMPL(4*1024)
};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

} //namespace glf

GLF_ENUM_FLAG(glf::fs::perm)

#endif

